package com.unitd.frame.comm.utils.encrypt.base64;

import com.unitd.frame.comm.exception.BizException;
import com.unitd.frame.comm.utils.encrypt.base64.encoder.IEncoder;
import com.unitd.frame.comm.utils.encrypt.base64.encoder.impl.Base64Encoder;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * @desc Base64编解码工具类
 * Base64编码说明
 * Base64编码要求把3个8位字节（3*8=24）转化为4个6位的字节（4*6=24）,之后在6位的前面补两个0,形成8位一个字节的形式.
 * 如果剩下的字符不足3个字节,则用0填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现1或2个'='.
 * 为了保证所输出的编码位可读字符,Base64制定了一个编码表,以便进行统一转换.编码表的大小为2^6=64,这也是Base64名称的由来.
 * @filename Base64.java
 * @copyright www.pactera.com.cn
 * @author Hudan
 * @version 1.0
 * @date 2017/8/14
 */
public class Base64 {
	private static final IEncoder encoder = new Base64Encoder();

	public static String toBase64String(byte[] data) {
		return toBase64String(data, 0, data.length);
	}

	public static String toBase64String(byte[] data, int off, int length) {
		byte[] encoded = encode(data, off, length);
		return fromByteArray(encoded);
	}

	/**
	 * @desc 将一个8位字符的数组转换成一个字符串
	 * @param bytes 8 bit characters.8位字符的字节数组
	 * @return String
	 */
	public static String fromByteArray(byte[] bytes) {
		return new String(asCharArray(bytes));
	}

	/**
	 * @desc 简单地将8位字符数组转换为字符数组
	 * @param bytes 8位字符的字节数组
	 * @return char[]
	 */
	public static char[] asCharArray(byte[] bytes) {
		char[] chars = new char[bytes.length];

		for (int i = 0; i != chars.length; i++) {
			chars[i] = (char) (bytes[i] & 0xff);
		}

		return chars;
	}

	/**
	 * @desc 对输入数据进行编码,生成一个base64编码的字节数组
	 * @param data 需要进行编码的字节数组
	 * @return 进行了base64编码的字节数组
	 */
	public static byte[] encode(byte[] data) {
		return encode(data, 0, data.length);
	}

	/**
	 * @desc 对输入数据进行编码,生成一个base64编码的字节数组
	 * @param data 需要进行编码的字节数组
	 * @param off 游标
	 * @param length 编码长度
	 * @return 进行了base64编码的字节数组
	 */
	public static byte[] encode(byte[] data, int off, int length) {
		int len = (length + 2) / 3 * 4;
		ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);

		try {
			encoder.encode(data, off, length, bOut);
		} catch (Exception e) {
			throw new BizException("exception encoding base64 string: " + e.getMessage(), e);
		}

		return bOut.toByteArray();
	}

	/**
	 * @desc 将字节数据编码为64,将其写入给定的输出流
	 * @param data 需要进行编码的字节数组
	 * @param out 输出流
	 * @return 生成的字节数
	 * @throws IOException
	 */
	public static int encode(byte[] data, OutputStream out) throws IOException {
		return encoder.encode(data, 0, data.length, out);
	}

	/**
	 * @desc 将字节数据编码为64,将其写入给定的输出流
	 * @param data 需要进行编码的字节数组
	 * @param off 游标
	 * @param length 编码长度
	 * @param out 输出流
	 * @return 生成的字节数
	 * @throws IOException
	 */
	public static int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
		return encoder.encode(data, off, length, out);
	}

	/**
	 * @desc 解码base64编码的输入数据, 假设输入数据是有效的
	 * @param data 需要进行解码的字节数组
	 * @return 解码后字节数组
	 */
	public static byte[] decode(byte[] data) {
		int len = data.length / 4 * 3;
		ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);

		try {
			encoder.decode(data, 0, data.length, bOut);
		} catch (Exception e) {
			throw new BizException("unable to decode base64 data: " + e.getMessage(), e);
		}

		return bOut.toByteArray();
	}

	/**
	 * @desc 解码base64编码的字符串数据——空格将被忽略
	 * @param data 需要进行解码的字符串
	 * @return 解码后字节数组
	 */
	public static byte[] decode(String data) {
		int len = data.length() / 4 * 3;
		ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);

		try {
			encoder.decode(data, bOut);
		} catch (Exception e) {
			throw new BizException("unable to decode base64 string: " + e.getMessage(), e);
		}

		return bOut.toByteArray();
	}

	/**
	 * @desc  解码base64编码的字符串数据,将其写入给定的输出流,空格字符将被忽略
	 * @param data base64编码的字符串
	 * @param out 输出流
	 * @return 生成的字节数
	 * @throws IOException
	 */
	public static int decode(String data, OutputStream out) throws IOException {
		return encoder.decode(data, out);
	}
}